47  Python条件语句和循环语句

本章定位(复习与补充训练层):本章对应正课第8章《If条件语句 制作交易条件单》(章节 8)与第9章《循环语句 股价涨跌幅提醒》(章节 9),用于复习、补缺与额外练习。建议先不看讲解,直接尝试下方平台任务,再对照解析补弱项。本章不属于必修主线,并与第39章《控制结构》(章节 40)互为补充训练(本章为分项强化版)。建议先不看讲解,直接尝试下方平台任务;先做本章『动手与思考』第 1 题与平台任务自测,通过即可跳过本章。

47.1 引言控制结构与算法设计

控制结构决定程序”先做什么、后做什么、何时重复”。本章以平台四任务为骨架,复习 if-elif-elseforwhilebreak/continue/pass 的配合使用。

47.2 本章学习目标

通过本章的复习与补充训练,你将能够:

  1. 不看正文,手写 if-elif-else 多分支结构,说明缩进、冒号与真值测试在分支选择中的作用
  2. for 遍历列表、字典(items()enumerate())与 range(),并用 while 写出一定可以终止的价格监控循环
  3. 区分 breakcontinuepass 的执行效果,判断循环 else 子句是否会被执行
  4. 把”遍历数据列→逐元素判断→收集结果”的筛选套路迁移到新的金融数据(如指数日涨跌幅)上,独立完成平台四个任务

47.3 if条件语句

动手写分支之前,先把理论坐标立起来:任何算法都可以用三种基本控制结构组合表示。

理论背景:控制流的三种基本结构

从计算科学理论的角度,任何算法都可以用三种基本控制结构表示:

  1. 顺序结构(Sequence): 语句按顺序执行
  2. 选择结构(Selection): 根据条件选择执行路径
  3. 循环结构(Iteration): 重复执行代码块

这三种结构构成了结构化程序设计(Structured Programming)的基础,由Edsger W. Dijkstra在1968年提出。Python通过if-elif-elseforwhile等关键字实现这三种结构。

列表 47.1: if-else条件分支与交易信号生成
# 场景:基于移动平均线的交易策略
# 定义关键价格指标
price = 18.50    # 当前股价
ma20 = 18.00     # 20日移动平均线
ma60 = 17.50     # 60日移动平均线

# if语句:条件判断与分支执行
# 语法: if condition1: elif condition2: ... else:
# if、elif、else后的冒号(:)表示代码块开始

# 条件1:价格同时高于MA20和MA60
# and逻辑运算符:两个条件都为True时,整体为True
if price > ma20 and price > ma60:
    signal = '买入'
    # 缩进的代码块属于if语句
    # Python使用缩进(通常4个空格)表示代码块层次

# 条件2:价格高于MA20但低于MA60
# elif是"else if"的缩写,用于处理多个互斥条件
elif price > ma20:
    signal = '持有'

# 条件3:价格低于MA60
elif price < ma60:
    signal = '卖出'

# else:以上条件都不满足时执行
# 在这里是:价格介于MA60和MA20之间
else:
    signal = '观望'

# 输出交易信号
# f-string是Python 3.6+的格式化字符串语法
print(f'交易信号: {signal}')
# 输出: 交易信号: 买入

上面 price > ma20 and price > ma60 这类条件的判断依据,是布尔逻辑。

数学背景:布尔逻辑与条件判断

Python的条件判断基于布尔代数(Boolean Algebra),由George Boole在1854年创立:

运算 数学符号 Python运算 交换律 结合律 分配律
与(AND) \(a \land b\) a and b
或(OR) \(a \lor b\) a or b
非(NOT) \(\neg a\) not a - - -

短路径求值(Short-Circuit Evaluation):

# Python的and/or运算采用短路径求值
# 一旦能够确定结果,立即停止计算
True and some_function()  # 会调用some_function()
False and some_function()  # 不会调用some_function(),直接返回False

代码深度解析:

  1. 条件表达式的真值测试:

    # Python中的真假值判定规则
    # 以下值被判定为False:
    # - False、None、数值0(0, 0.0)、空字符串('')、空容器([], {}, set())
    
    # 任何其他值都被判定为True
    # 这称为"真值测试"(Truth Value Testing)
    
    # 示例:检查持仓是否为空
    position = 0
    if position:
        print(f'持仓{position}股')
    else:
        print('空仓')  # 会执行这个分支
    
    # 显式比较(推荐)
    if position != 0:
        print(f'持仓{position}股')
    else:
        print('空仓')
  2. 链式比较(Chained Comparison):

    # Python支持链式比较,数学上更自然
    price = 15.0
    
    # 方法1:链式比较(Python风格)
    if 10 < price < 20:
        print('价格在10到20之间')
    
    # 等价于方法2:逻辑与运算
    if price > 10 and price < 20:
        print('价格在10到20之间')
    
    # 链式比较更简洁,且price只计算一次
  3. 三元运算符(Ternary Operator):

    # Python的三元表达式: 值1 if 条件 else 值2
    # 适用于简单的条件赋值
    
    # 示例:计算交易手续费
    amount = 10000
    commission_rate = 0.0003 if amount > 10000 else 0.0005
    # 如果金额>10000,费率0.03%,否则0.05%
    
    print(f'手续费率: {commission_rate:.4f}')
    
    # 等价的if-else语句
    if amount > 10000:
        commission_rate = 0.0003
    else:
        commission_rate = 0.0005
  4. 嵌套if语句的优化:

    # 不推荐:深层嵌套
    price = 18.5
    volume = 1000000
    
    if price > 18:
        if volume > 500000:
            if price > ma20:
                signal = '强力买入'
            else:
                signal = '考虑买入'
        else:
            signal = '观望'
    else:
        signal = '不操作'
    
    # 推荐:提前返回(卫语句)
    def generate_signal(price, volume, ma20):
        if price <= 18:
            return '不操作'
        if volume <= 500000:
            return '观望'
        if price > ma20:
            return '强力买入'
        return '考虑买入'
    
    signal = generate_signal(price, volume, ma20)
  5. 金融应用:多条件筛选:

    # 股票筛选策略
    stock = {
        'name': '招商银行',
        'price': 45.2,
        'pe': 8.5,      # 市盈率
        'pb': 0.8,      # 市净率
        'roe': 0.15     # 净资产收益率
    }
    
    # 多条件筛选
    conditions = [
        stock['price'] < 100,        # 价格<100
        stock['pe'] < 10,            # PE<10
        stock['pb'] < 1,             # PB<1
        stock['roe'] > 0.10          # ROE>10%
    ]
    
    # all()函数:所有条件为True时返回True
    if all(conditions):
        print(f'{stock["name"]} 符合价值投资标准')
    else:
        print(f'{stock["name"]} 不符合标准')

看完 if-elif-else 的多分支写法,初学者常会问:Python 为什么不像 C 语言那样提供 switch-case?

补充说明:为什么Python没有switch-case?

Python 3.10之前没有传统的switch-case语句,这是有意的设计选择:

  1. if-elif-else已经足够清晰
  2. 字典映射可以替代switch-case
  3. 避免C语言switch的fall-through陷阱

Python 3.10引入了match-case语句,提供了更强大的模式匹配能力。

47.4 for循环

列表 47.2: for循环遍历数据结构
# 1. 遍历列表
# 创建股票代码列表
stocks = ['600519.SH', '000858.SZ', '600036.SH']

print('股票代码:')
# for循环:遍历序列中的每个元素
# 语法: for item in iterable:
for code in stocks:
    # code依次取stocks中的每个元素
    print(f'  {code}')
# 输出:
#   600519.SH
#   000858.SZ
#   600036.SH

# 2. 遍历字典
stock_dict = {'600519.SH': '贵州茅台', '000858.SZ': '五粮液'}

print('\n股票名称:')
# items()方法返回(键, 值)元组的迭代器
# 这允许同时遍历键和值
for code, name in stock_dict.items():
    # 每次迭代,code和name分别取键和值
    print(f'  {code}: {name}')

# 3. enumerate()获取索引和元素
print('\n带索引的遍历:')
# enumerate()返回(索引, 元素)的迭代器
# start参数指定索引起始值,默认为0
for i, code in enumerate(stocks):
    # i从0开始,依次为0, 1, 2
    print(f'  第{i+1}个: {code}')
# 输出:
#   第1个: 600519.SH
#   第2个: 000858.SZ
#   第3个: 600036.SH

代码深度解析:

  1. for循环的执行流程:

    # for循环的等价while循环实现
    iterable = ['a', 'b', 'c']
    iterator = iter(iterable)  # 获取迭代器
    
    # 方式1:for循环(Python风格)
    for item in iterable:
        print(item)
    
    # 方式2:while循环(等价实现)
    try:
        while True:
            item = next(iterator)  # 获取下一个元素
            print(item)
    except StopIteration:
        pass  # 迭代结束
    
    # for循环本质上是迭代器协议的语法糖
  2. range()函数详解:

    # range()生成整数序列,常用于for循环
    # 语法: range(start, stop, step)
    
    # 用法1:指定结束值
    for i in range(5):
        print(i)  # 输出: 0, 1, 2, 3, 4
    
    # 用法2:指定起始和结束值
    for i in range(2, 5):
        print(i)  # 输出: 2, 3, 4
    
    # 用法3:指定步长
    for i in range(0, 10, 2):
        print(i)  # 输出: 0, 2, 4, 6, 8
    
    # 金融应用:生成交易日序列
    import pandas as pd
    dates = pd.date_range('2024-01-01', periods=5)
    
    for i in range(len(dates)):
        print(f'第{i+1}个交易日: {dates[i].date()}')
  3. zip()函数并行遍历:

    # zip()可以同时遍历多个序列
    codes = ['600519.SH', '000858.SZ', '600036.SH']
    prices = [1850, 220, 45]
    
    # 同时遍历代码和价格
    for code, price in zip(codes, prices):
        print(f'{code}: {price}元')
    
    # 使用enumerate和zip组合
    for i, (code, price) in enumerate(zip(codes, prices)):
        print(f'{i+1}. {code}: {price}元')
  4. 字典的多种遍历方式:

    portfolio = {
        '600519.SH': {'name': '茅台', 'shares': 100},
        '000858.SZ': {'name': '五粮液', 'shares': 200}
    }
    
    # 方式1:遍历键
    print('股票代码:')
    for code in portfolio:
        print(f'  {code}')
    
    # 方式2:遍历键值对
    print('\n股票详情:')
    for code, info in portfolio.items():
        print(f'  {code}: {info["name"]}, {info["shares"]}股')
    
    # 方式3:遍历值
    print('\n股票名称:')
    for info in portfolio.values():
        print(f'  {info["name"]}')
  5. 列表推导式(List Comprehension):

    # 列表推导式是for循环的简洁表达
    # 语法: [expression for item in iterable if condition]
    
    # 传统for循环
    squares = []
    for i in range(10):
        squares.append(i ** 2)
    
    # 列表推导式(Python风格)
    squares = [i ** 2 for i in range(10)]
    
    # 带条件的列表推导式
    even_squares = [i ** 2 for i in range(10) if i % 2 == 0]
    print(even_squares)  # [0, 4, 16, 36, 64]
    
    # 金融应用:筛选符合条件的股票
    stocks = [
        {'name': '茅台', 'pe': 45},
        {'name': '五粮液', 'pe': 35},
        {'name': '招行', 'pe': 8}
    ]
    
    # 找出PE<20的股票
    value_stocks = [s for s in stocks if s['pe'] < 20]
    print(f'价值股: {[s["name"] for s in value_stocks]}')

47.5 while循环

列表 47.3: while循环与价格监控
# 场景:监控股价直到达到目标价
# 定义初始变量
price = 18.00   # 当前价格
target = 20.00  # 目标价格
days = 0        # 已监控天数

# while循环:当条件为True时重复执行
# 语法: while condition:
# 注意:必须确保循环能够终止,否则会形成无限循环

while price < target:
    # 每天价格增长0.50元
    # += 是增量赋值操作符,等价于 price = price + 0.50
    price += 0.50

    # 天数加1
    days += 1

    # 输出当前状态
    # :.2f 表示格式化为两位小数
    print(f'第{days}天, 价格: {price:.2f}元')

    # 安全措施:防止无限循环
    # 如果超过100天仍未达到目标价,强制退出
    if days > 100:
        print('达到最大监控天数')
        break  # break语句立即跳出循环

print(f'\n达到目标价耗时{days}天')

代码深度解析:

  1. while vs for循环的选择:

    # for循环:已知迭代次数
    for i in range(10):
        print(i)
    
    # while循环:未知迭代次数,根据条件决定
    i = 0
    while i < 10:
        print(i)
        i += 1
    
    # 选择原则:
    # - 遍历序列 → 用for
    # - 等待条件满足 → 用while
  2. while循环的常见模式:

    # 模式1:计数器循环
    count = 0
    while count < 5:
        print(count)
        count += 1
    
    # 模式2:无限循环+break
    while True:
        user_input = input('输入命令(quit退出):')
        if user_input == 'quit':
            break
        process_command(user_input)
    
    # 模式3:标志变量
    running = True
    while running:
        # 执行某些操作
        if should_stop():
            running = False
  3. while-else结构:

    # while循环可以带else子句
    # else在循环正常结束时执行(break跳出不执行)
    
    count = 0
    while count < 3:
        print(count)
        count += 1
    else:
        print('循环正常结束')  # 会执行
    
    # 对比:使用break的情况
    count = 0
    while count < 3:
        print(count)
        count += 1
        if count == 2:
            break
    else:
        print('循环正常结束')  # 不会执行
  4. 金融应用:蒙特卡洛模拟:

    import random
    
    # 模拟股价路径,直到达到止损或止盈
    price = 100
    stop_loss = 95   # 止损价
    take_profit = 105  # 止盈价
    days = 0
    
    while stop_loss < price < take_profit:
        # 每日收益率服从正态分布
        # 均值0.001,标准差0.02
        daily_return = random.gauss(0.001, 0.02)
        price *= (1 + daily_return)
        days += 1
    
        # 安全措施
        if days > 252:  # 最多模拟一年
            break
    
    if price >= take_profit:
        print(f'第{days}天止盈, 价格: {price:.2f}元')
    elif price <= stop_loss:
        print(f'第{days}天止损, 价格: {price:.2f}元')
    else:
        print(f'第{days}天未达目标, 价格: {price:.2f}元')
  5. 循环的性能考虑:

    # while循环可能比for循环慢
    import time
    
    # 测试for循环
    start = time.time()
    for i in range(1000000):
        _
    print(f'for循环: {time.time() - start:.4f}秒')
    
    # 测试while循环
    start = time.time()
    i = 0
    while i < 1000000:
        _
        i += 1
    print(f'while循环: {time.time() - start:.4f}秒')
    
    # for循环通常更快,因为迭代器协议更优化

47.6 循环控制

列表 47.4: break和continue语句
# 1. break语句:立即跳出循环
# 场景:查找第一个负收益并判断是否需要止损
returns = [0.05, -0.02, 0.01, -0.015, 0.03]

print('查找负收益:')
# enumerate()同时获取索引和值
for i, ret in enumerate(returns):
    # 检查是否为负收益
    if ret < 0:
        # :.2% 表示格式化为百分比,保留两位小数
        print(f'  第{i+1}天: {ret:.2%}')

        # 检查是否需要止损(亏损超过1%)
        if ret < -0.01:
            print('  触发止损,停止监控')
            break  # 立即跳出循环,不再检查后续元素

# 2. continue语句:跳过本次迭代,继续下一次
print('\n正收益日:')
for ret in returns:
    # 如果是负收益,跳过本次迭代
    if ret < 0:
        continue  # 直接进入下一次循环,不执行后面的代码

    # 只有正收益才会执行这里
    print(f'  {ret:.2%}')

代码深度解析:

  1. break的执行流程:

    # break只跳出最近的一层循环
    for i in range(3):
        for j in range(3):
            if j == 1:
                break  # 只跳出内层循环
            print(f'i={i}, j={j}')
    
    # 输出:
    # i=0, j=0
    # i=1, j=0
    # i=2, j=0
    
    # 如果要跳出多层循环,需要使用标志变量
    found = False
    for i in range(3):
        if found:
            break
        for j in range(3):
            if j == 1:
                found = True
                break
  2. continue的执行流程:

    # continue跳过本次迭代的剩余代码
    for i in range(5):
        if i % 2 == 0:
            continue  # 跳过偶数
        print(i)  # 只打印奇数
    
    # 输出: 1, 3, 5
    
    # continue的等价写法
    for i in range(5):
        if i % 2 != 0:  # 反转条件
            print(i)
  3. 循环else子句与break:

    # break会阻止else子句的执行
    
    # 情况1:正常结束(执行else)
    for i in range(3):
        if i > 10:
            break
    else:
        print('循环正常结束,未找到')  # 会执行
    
    # 情况2:break结束(不执行else)
    for i in range(3):
        if i > 1:
            break
    else:
        print('循环正常结束,未找到')  # 不会执行
  4. 金融应用:订单撮合:

    # 模拟订单撮合过程
    buy_orders = [
        {'price': 18.50, 'quantity': 1000},
        {'price': 18.45, 'quantity': 500},
        {'price': 18.40, 'quantity': 2000}
    ]
    
    sell_orders = [
        {'price': 18.35, 'quantity': 800},
        {'price': 18.40, 'quantity': 1500}
    ]
    
    # 按价格排序(买单降序,卖单升序)
    buy_orders.sort(key=lambda x: -x['price'])
    sell_orders.sort(key=lambda x: x['price'])
    
    # 撮合逻辑
    for buy in buy_orders:
        for sell in sell_orders:
            # 如果买单价格低于卖单价格,无法成交
            if buy['price'] < sell['price']:
                print(f'买单{buy["price"]}元低于卖单{sell["price"]}元,停止撮合')
                break  # 跳出内层循环
    
            # 计算成交量
            trade_quantity = min(buy['quantity'], sell['quantity'])
    
            print(f'成交: 价格{sell["price"]}元, 数量{trade_quantity}股')
    
            # 更新剩余数量
            buy['quantity'] -= trade_quantity
            sell['quantity'] -= trade_quantity
    
            # 如果卖单已完全成交,继续下一个卖单
            if sell['quantity'] == 0:
                continue
    
            # 如果买单已完全成交,跳出内层循环
            if buy['quantity'] == 0:
                break
  5. pass语句:占位符:

    # pass语句什么都不做,用作占位符
    
    # 场景1:空函数体
    def placeholder_function():
        pass  # TODO: 待实现
    
    # 场景2:空类
    class PlaceholderClass:
        pass
    
    # 场景3:条件分支占位
    if condition:
        pass  # 待添加代码
    else:
        do_something()

47.7 综合应用股票筛选

列表 47.5: 综合应用:股票筛选策略
# 模拟股票数据
# 每个股票是一个字典,包含代码、价格、PE等属性
stocks = [
    {'code': '600519.SH', 'price': 1850, 'pe': 45},
    {'code': '000858.SZ', 'price': 220, 'pe': 35},
    {'code': '600036.SH', 'price': 45, 'pe': 8}
]

# 筛选策略:价格<500且PE<40的价值股
print('筛选结果:')

# 遍历所有股票
for stock in stocks:
    # 条件1:价格<500(排除高价股)
    if stock['price'] >= 500:
        # continue:跳过本次迭代,继续下一个股票
        continue

    # 条件2:PE<40(合理估值)
    # 只有价格条件满足的股票才会到达这里
    if stock['pe'] < 40:
        # 两个条件都满足,输出股票信息
        print(f"  {stock['code']}: 价格={stock['price']}元, PE={stock['pe']}")
    # 如果PE>=40,什么都不做,继续下一个股票

代码深度解析:

  1. 控制流程图:

    # 本例的执行流程:
    #
    # 开始
    #   ↓
    # 遍历stocks列表
    #   ↓
    # 取第一个股票{'code': '600519.SH', 'price': 1850, 'pe': 45}
    #   ↓
    # 检查price>=500?
    #   ├─ 是 → continue → 跳过,取下一个股票
    #   └─ 否 → 继续检查
    #       ↓
    #       检查pe<40?
    #       ├─ 是 → 输出股票信息
    #       └─ 否 → 什么都不做
    #   ↓
    # 取下一个股票,重复上述流程
    #   ↓
    # 所有股票处理完毕
    #   ↓
    # 结束
  2. 筛选逻辑的优化:

    # 方法1:原始版本(本例使用)
    for stock in stocks:
        if stock['price'] >= 500:
            continue
        if stock['pe'] < 40:
            print(f"{stock['code']}: PE={stock['pe']}元")
    
    # 方法2:单个复合条件
    for stock in stocks:
        if stock['price'] < 500 and stock['pe'] < 40:
            print(f"{stock['code']}: PE={stock['pe']}元")
    
    # 方法3:列表推导式(最Pythonic)
    filtered = [s for s in stocks if s['price'] < 500 and s['pe'] < 40]
    for stock in filtered:
        print(f"{stock['code']}: PE={stock['pe']}元")
    
    # 方法4:filter()函数(函数式风格)
    filtered = filter(
        lambda s: s['price'] < 500 and s['pe'] < 40,
        stocks
    )
    for stock in filtered:
        print(f"{stock['code']}: PE={stock['pe']}元")
  3. 多策略组合:

    # 更复杂的筛选场景
    stocks = [
        {'code': '600519.SH', 'price': 1850, 'pe': 45, 'pb': 10, 'dividend': 1.5},
        {'code': '000858.SZ', 'price': 220, 'pe': 35, 'pb': 8, 'dividend': 1.2},
        {'code': '600036.SH', 'price': 45, 'pe': 8, 'pb': 0.8, 'dividend': 2.5},
        {'code': '601318.SH', 'price': 50, 'pe': 9, 'pb': 0.9, 'dividend': 2.0}
    ]
    
    # 策略1:价值投资(低PE低PB)
    print('=== 价值投资策略 ===')
    for stock in stocks:
        if stock['pe'] < 20 and stock['pb'] < 1:
            print(f"{stock['code']}: PE={stock['pe']}, PB={stock['pb']}")
    
    # 策略2:红利策略(高股息)
    print('\n=== 红利策略 ===')
    for stock in stocks:
        if stock['dividend'] > 2.0:
            print(f"{stock['code']}: 股息率={stock['dividend']}%")
    
    # 策略3:成长策略(合理估值)
    print('\n=== 成长策略 ===')
    for stock in stocks:
        if 10 < stock['pe'] < 30 and stock['price'] < 100:
            print(f"{stock['code']}: PE={stock['pe']}, 价格={stock['price']}")
  4. 性能优化:提前过滤:

    # 优化前:多次遍历
    results = []
    for stock in stocks:
        if stock['price'] < 500:
            if stock['pe'] < 40:
                if stock['pb'] < 5:
                    results.append(stock)
    
    # 优化后:将最严格的条件放在前面
    # 这样可以尽早排除不符合条件的元素
    results = []
    for stock in stocks:
        # 价格条件最严格,先检查
        if stock['price'] >= 500:
            continue
        # PE条件次之
        if stock['pe'] >= 40:
            continue
        # PB条件最后
        if stock['pb'] >= 5:
            continue
    
        results.append(stock)
  5. 实际应用:动态止损策略:

    # 动态调整止损策略
    positions = [
        {'code': '600519.SH', 'entry_price': 1800, 'current_price': 1850},
        {'code': '000858.SZ', 'entry_price': 230, 'current_price': 220},
        {'code': '600036.SH', 'entry_price': 48, 'current_price': 45}
    ]
    
    # 止损策略:亏损超过5%且亏损超过3元
    print('止损信号:')
    for pos in positions:
        # 计算收益率
        ret = (pos['current_price'] - pos['entry_price']) / pos['entry_price']
    
        # 计算亏损金额
        loss = pos['entry_price'] - pos['current_price']
    
        # 检查止损条件
        if ret < -0.05 and loss > 3:
            print(f"{pos['code']}: 亏损{ret:.2%}, 亏损{loss:.0f}元, 建议止损")
        elif ret < 0:
            print(f"{pos['code']}: 亏损{ret:.2%}, 继续持有")
        else:
            print(f"{pos['code']}: 盈利{ret:.2%}, 继续持有")

最佳实践总结:

  1. 选择合适的循环结构:
    • 遍历序列 → 用for循环
    • 等待条件 → 用while循环
    • 已知次数 → 用for + range()
    • 未知次数 → 用while条件
  2. 避免常见陷阱:
    • 无限循环:确保while循环有终止条件
    • 修改遍历对象:不要在遍历时修改列表
    • 过深嵌套:超过3层嵌套考虑重构
    • 循环中的计算:将不变的计算移到循环外
  3. 性能优化技巧:
    • 使用列表推导式替代简单的for循环
    • 使用生成器表达式处理大数据
    • 将最严格的条件放在前面
    • 使用内置函数(sum(), max(), min())替代循环
  4. 可读性提升:
    • 使用有意义的变量名
    • 提取复杂条件为函数
    • 添加注释说明逻辑意图
    • 保持函数简短(单一职责)

平台任务(平台原始代码)

以下代码与教学平台任务要求完全一致:

任务要求:任务一,遍历道琼斯、恒生、上证、深证四列日涨跌幅,把每列收集成列表并依次打印;任务二,遍历道琼斯列表,遇到小于 -0.006 的数据即用 break 跳出;任务三,遍历恒生列表,上涨日用 pass 跳过、只输出下跌日;任务四,把上证综指处于设定区间内的日涨跌幅收集到新列表并打印。请将代码原样输入教学平台(注释除外),判定以平台为准。

列表 47.6: 平台原始代码 (1)-(4)
# 注:该代码块读取教学平台的 OSS 镜像直链(联网即可在本地复现)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务一
import pandas as pd

# 从Excel文件读取数据存入data
data=pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/1725522421307.xlsx")

DJ_index = []  # 定义列表DJ_index
HS_index= []  # 定义列表HS_index
SH_index = []  # 定义列表SH_index
SZ_index = []  # 定义列表SZ_index

for i in data["道琼斯工业平均指数"]:  # 遍历data["道琼斯工业平均指数"]中的每个i
    DJ_index.append(i)  # 将道琼斯指数涨跌幅数据添加到列表
  
for i in data["恒生指数"]:     #获取恒生指数的日涨跌幅列表
    HS_index.append(i)  # 将恒生指数涨跌幅数据添加到列表

for i in data["上证综指"]:     #获取上证综指的日涨跌幅列表
    SH_index.append(i)  # 将上证综指涨跌幅数据添加到列表
  
for i in data["深证成指"]:  # 遍历data["深证成指"]中的每个i
    SZ_index.append(i)  # 将深证成指涨跌幅数据添加到列表

print(DJ_index)  # 输出指数数据
print(HS_index)  # 输出指数数据
print(SH_index)  # 输出指数数据
print(SZ_index)  # 输出指数数据


#任务二
import pandas as pd

# 从Excel文件读取数据存入data
data=pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/1725522421307.xlsx")

DJ_index = []  # 定义列表DJ_index
for i in data["道琼斯工业平均指数"]:  # 遍历data["道琼斯工业平均指数"]中的每个i
    DJ_index.append(i)  # 将道琼斯指数涨跌幅数据添加到列表

for i in DJ_index:  # 遍历DJ_index中的每个i
  if i < -0.006:    #如果小于-0.006
    break  # 跳出循环
  print("已经访问的道琼斯工业平均指数日涨跌幅数据",i)  # 输出已经访问的道琼斯工业平均指数日涨跌幅数据
  
#任务三
import pandas as pd

# 从Excel文件读取数据存入data
data=pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/1725522421307.xlsx")

HS_index = []  # 定义列表HS_index

for i in data["恒生指数"]:  # 遍历data["恒生指数"]中的每个i
    HS_index.append(i)  # 将恒生指数涨跌幅数据添加到列表
for i in HS_index:  # 遍历HS_index中的每个i
  if i > 0:       #大于零的话
    pass  # 占位符,暂不执行任何操作
  else:  # 不满足以上条件时
    print("恒生指数的日下跌数据(负数)",i)  # 输出恒生指数的日下跌数据(负数)

 
#任务四
import pandas as pd

# 从Excel文件读取数据存入data
data=pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/1725522421307.xlsx")

SH_index = []  # 定义列表SH_index
SZ_index = []  # 定义列表SZ_index

for i in data["上证综指"]:  # 遍历data["上证综指"]中的每个i
    SH_index.append(i)  # 将上证综指涨跌幅数据添加到列表
  
for i in data["深证成指"]:  # 遍历data["深证成指"]中的每个i
    SZ_index.append(i)  # 将深证成指涨跌幅数据添加到列表
  
R_SH = []        #创建一个R_SH的空列表
for i in SH_index:  # 遍历SH_index中的每个i
  if i < -0.1:     #如果小于-0.1
    pass  # 占位符,暂不执行任何操作
  elif i > 0.01:    #如果大于0.01
    pass  # 占位符,暂不执行任何操作
  else:  # 不满足以上条件时
        R_SH.append(i)  # 将上证综指筛选后的涨跌幅数据添加到列表
print("上证综指的日涨跌幅处于-1%至1%区间的列表\n",R_SH)  # 输出上证综指的日涨跌幅处于-1%至1%区间的列表\n

预期输出(OSS 直链本机实测,数据文件为 2024-08-01 至 2024-08-16 共 12 个交易日的四大指数日涨跌幅;具体以平台运行结果为准):

任务一依次打印道琼斯、恒生、上证、深证四个列表,每个 12 个数值:

[-0.0121, -0.0151, -0.026, 0.0076, -0.006, 0.0176, 0.0013, -0.0036, 0.0104, 0.0061, 0.0139, 0.0024]
[-0.0023, -0.0208, -0.0146, -0.0031, 0.0138, 0.0008, 0.0117, 0.0013, 0.0036, -0.0035, -0.0002, 0.0188]
[-0.0022, -0.0092, -0.0154, 0.0023, 0.0009, 0.0, -0.0027, -0.0014, 0.0034, -0.006, 0.0094, 0.0007]
[-0.0092, -0.0138, -0.0185, 0.0082, -0.0017, -0.0004, -0.0062, -0.0024, 0.0043, -0.0117, 0.0071, -0.0024]
  • 任务二在当前平台数据下首日(2024-08-01 道琼斯 -0.0121)即触发 break,无任何输出行——这不是代码坏了,而是首观测就满足终止条件
  • 任务三打印 6 行”恒生指数的日下跌数据(负数)“,对应恒生列表中的全部 6 个负值,依次为 -0.0023、-0.0208、-0.0146、-0.0031、-0.0035、-0.0002
  • 任务四先打印一行”上证综指的日涨跌幅处于-1%至1%区间的列表”,随后输出 12 个数值——当前数据全部落在代码设定的 [-0.1, 0.01] 区间内(注意下限是 -0.1 即 -10%,而非输出文字所称的 -1%),故该列表与上证综指原始列表完全相同

47.8 本章小结

要点:

  • 三种控制结构:顺序、选择(if-elif-else)、循环(for/while);for 适合遍历已知序列,while 适合等待条件满足
  • 条件判断基于真值测试:数值 0、空字符串、空容器、NoneFalse 被判定为假,其余为真;多条件用 and/or/not 组合,支持链式比较(10 < price < 20)
  • for 的常用搭档:range(start, stop, step) 生成整数序列,enumerate() 同时取序号与元素,items() 同时取字典的键与值
  • 循环控制三兄弟:break 立即跳出所在层循环,continue 跳过本次迭代,pass 仅作占位;循环正常走完才执行 else 子句,被 break 打断则不执行
  • 筛选套路:遍历→条件分支决定”收集/跳过/终止”,可等价改写为复合条件判断或列表推导式,后者更简洁

易错点:

  • if/elif/else 后的冒号与 4 空格缩进决定代码块归属,缩进不一致会改变分支逻辑甚至直接报错
  • while 循环忘记更新循环变量会陷入无限循环,实务上应设置最大迭代次数等安全阀
  • break 只跳出最近一层循环,多层循环需要借助标志变量逐层退出
  • 遍历列表的同时原地修改列表,容易漏元素或越界,应遍历副本或改用推导式生成新列表
  • 平台任务二把 if i < -0.006: break 写在 print 之前,触发终止当天的数据不会被输出;先打印再判断会多出一行结果;且在当前平台数据下首日(2024-08-01,道琼斯 -0.0121)就满足终止条件,整个循环一行都不输出,属正常现象而非代码出错

47.9 动手与思考

以下练习每题附参考答案(默认折叠)。请先独立完成并写下你的判断,再点开对照,最后上机验证。

  1. 输出预测:不运行代码,先写出下面代码的输出结果,再上机检验你的判断。

    returns = [0.03, -0.008, 0.012, -0.02]
    count = 0
    for r in returns:
        if r < 0:
            count += 1
            if r < -0.01:
                break
    print(count)

    参考答案(先写下你的预测再点开)

    解题思路:逐个跟踪 count0.03 不小于 0,跳过;-0.008 小于 0,count 变 1,但 -0.008 < -0.01 不成立(-0.008 比 -0.01 大),不触发 break0.012 跳过;-0.02 小于 0,count 变 2,且 -0.02 < -0.01 成立,break 终止循环。最终输出 2——内层 if 只统计下跌日,break 只在跌幅超过 1% 时触发。

    # 验证脚本:原样运行题面代码
    returns = [0.03, -0.008, 0.012, -0.02]  # 四个交易日的收益率
    count = 0  # 下跌日计数器
    for r in returns:  # 遍历收益率序列
        if r < 0:  # 下跌日
            count += 1  # 计数加一
            if r < -0.01:  # 跌幅超过1%
                break  # 立即终止循环
    print(count)  # 输出计数值

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    2

    回扣主线章break 与计数循环的讲解见第 章节 9 章与第 章节 8 章。

  2. 自测回忆:不看正文,完整列举 Python 中被判定为 False 的值;再说明 elif price > ma20 这个分支在什么前提下才会被求值。

    参考答案(点开前请先独立完成)

    解题思路:被判定为 False 的值——布尔值 FalseNone;数值零(整数 0、浮点 0.0、复数 0j);空序列与空容器(空字符串 ''、空列表 []、空元组 ()、空字典 {}、空集合 set()、空 range(0))。其余一切值均为 True(注意 '0' 是非空字符串、为真)。elif price > ma20 只有在它之前的所有分支条件(如 if price > ma5: 等)都为 False、且轮到该分支时才会被求值——elif 链自上而下短路匹配,命中一个分支后其余分支不再判断。

    回扣主线章:真值测试与 if-elif-else 匹配顺序的讲解见第 章节 8 章。

  3. 变式任务(平台任务同型改造):把平台任务三改为”输出恒生指数的日上涨数据(正数)“,再进一步改为”只输出涨幅大于 1% 的交易日”;说明 pass 分支的条件应如何随之调整。

    参考答案(点开前请先独立完成)

    解题思路:平台任务三“输出下跌日”的结构是上涨日 pass、其余输出;改为“输出上涨日”只需把 pass 分支的条件反转为 i <= 0(下跌或平盘日跳过),输出分支打印正数;进一步“只输出涨幅大于 1%”则把跳过条件收紧为 i <= 0.01(不超过 1% 的都跳过)。规律:pass 分支承接“不想要的情形”,筛选标准提高时跳过条件随之收紧。

    # 变式程序:输出恒生指数的日上涨数据,以及涨幅大于1%的交易日
    import pandas as pd  # 导入pandas用于读取Excel
    data = pd.read_excel('https://huoran.oss-cn-shenzhen.aliyuncs.com/1725522421307.xlsx')  # 读取平台OSS直链的指数涨跌幅数据
    hs_index = []  # 定义恒生指数涨跌幅列表
    for i in data['恒生指数']:  # 遍历恒生指数列
        hs_index.append(i)  # 逐个收集为列表
    for i in hs_index:  # 第一问:输出上涨日
        if i <= 0:  # 下跌或平盘日
            pass  # 跳过
        else:
            print('恒生指数的日上涨数据(正数)', i)  # 输出上涨日
    for i in hs_index:  # 第二问:只输出涨幅大于1%的交易日
        if i <= 0.01:  # 涨幅不超过1%
            pass  # 跳过
        else:
            print('恒生指数涨幅大于1%的交易日', i)  # 输出大于1%的交易日

    预期输出(OSS 直链 2026-08-27 本机 peter 环境实际拉取运行结果,具体以平台运行结果为准;该直链为公开镜像,若失效请改在教学平台读取同一数据文件):

    恒生指数的日上涨数据(正数) 0.0138
    恒生指数的日上涨数据(正数) 0.0008
    恒生指数的日上涨数据(正数) 0.0117
    恒生指数的日上涨数据(正数) 0.0013
    恒生指数的日上涨数据(正数) 0.0036
    恒生指数的日上涨数据(正数) 0.0188
    恒生指数涨幅大于1%的交易日 0.0138
    恒生指数涨幅大于1%的交易日 0.0117
    恒生指数涨幅大于1%的交易日 0.0188

    注意:以上为本题变式的独立代码;列表 47.6 对应平台任务的原始代码块仍须原样输入教学平台,不要用本变式替换。

    回扣主线章:同型筛选循环的讲解见第 章节 9 章与第 章节 8 章。

  4. 变式任务:把平台任务一中的任意一个 for 循环改写为 while 循环,要求输出与原来完全一致,并说明你用什么条件保证循环一定终止。

    参考答案(点开前请先独立完成)

    解题思路:取任务一“把道琼斯列收集成列表”的 for 循环改写。for 版由序列长度自动控制次数;while 版需要自建位置索引 pos,循环条件写 pos < len(序列)——索引一旦越界条件即失效,循环必然终止,且循环体末尾必须 pos += 1 推进索引(漏写即无限循环)。两版输出完全一致。

    # 变式程序:任务一“收集道琼斯列”的for循环改写为while循环
    import pandas as pd  # 导入pandas用于读取Excel
    data = pd.read_excel('https://huoran.oss-cn-shenzhen.aliyuncs.com/1725522421307.xlsx')  # 读取平台OSS直链的指数涨跌幅数据
    dj_index = []  # 定义道琼斯涨跌幅列表
    pos = 0  # 位置索引,从0开始
    while pos < len(data['道琼斯工业平均指数']):  # 索引未越界就继续循环
        dj_index.append(data['道琼斯工业平均指数'][pos])  # 按索引收集元素
        pos += 1  # 推进索引,保证循环一定终止
    print(dj_index)  # 输出与任务一for循环完全一致的列表

    预期输出(OSS 直链 2026-08-27 本机 peter 环境实际拉取运行结果,具体以平台运行结果为准;该直链为公开镜像,若失效请改在教学平台读取同一数据文件):

    [-0.0121, -0.0151, -0.026, 0.0076, -0.006, 0.0176, 0.0013, -0.0036, 0.0104, 0.0061, 0.0139, 0.0024]

    注意:以上为本题变式的独立代码;列表 47.6 对应平台任务的原始代码块仍须原样输入教学平台,不要用本变式替换。

    回扣主线章:for 与 while 的选择准则见第 章节 9 章。

  5. 思考题:平台任务二用 break 在遇到第一个小于 -0.006 的数据时停止遍历。如果想知道”最后一个”满足条件的数据是什么,应如何改写循环?请分别用 forwhile 各实现一次。

    参考答案(点开前请先独立完成)

    解题思路:两种思路。思路一(正向遍历、记录后继续):把 break 换成“记录”——每次命中条件就更新 last_drop,循环自然走完,变量里留下的就是最后一个满足条件的数据;适合还要继续统计其他信息的场合。思路二(反向遍历、命中即停):索引从末尾往前走,while pos >= 0 且循环体内命中 dj_index[pos] < -0.006break——从后往前的第一个就是“最后一个”,效率更高(当前数据在第 3 个就命中,即 -0.026)。

    # 思考题解答:分别用for与while找出最后一个小于-0.006的数据
    import pandas as pd  # 导入pandas用于读取Excel
    data = pd.read_excel('https://huoran.oss-cn-shenzhen.aliyuncs.com/1725522421307.xlsx')  # 读取平台OSS直链的指数涨跌幅数据
    dj_index = list(data['道琼斯工业平均指数'])  # 道琼斯涨跌幅列表(同平台任务二)
    last_drop = None  # 记录最后一个满足条件的数据
    for i in dj_index:  # for版:正向遍历,命中就覆盖记录
        if i < -0.006:  # 满足平台任务二的条件
            last_drop = i  # 覆盖旧记录
    print('for版最后一个小于-0.006的数据:', last_drop)  # 输出最后命中的数据
    pos = len(dj_index) - 1  # while版:从末尾索引开始
    while pos >= 0:  # 索引未走完就继续
        if dj_index[pos] < -0.006:  # 从后往前首个命中
            print('while版最后一个小于-0.006的数据:', dj_index[pos])  # 输出该数据
            break  # 命中即最后一个,立即终止
        pos -= 1  # 索引前移

    预期输出(OSS 直链 2026-08-27 本机 peter 环境实际拉取运行结果,具体以平台运行结果为准;该直链为公开镜像,若失效请改在教学平台读取同一数据文件):

    for版最后一个小于-0.006的数据: -0.026
    while版最后一个小于-0.006的数据: -0.026

    回扣主线章break 的终止时机与循环改写的讲解见第 章节 9 章。